package be.kuleuven.cs.mop.domain.model.impl;


import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import be.kuleuven.cs.mop.domain.exceptions.BusinessRuleThreeViolationExceptionDeadlineExpired;
import be.kuleuven.cs.mop.domain.exceptions.BusinessRuleThreeViolationExceptionTaskScheduleDoesNotCoverCurrentTime;
import be.kuleuven.cs.mop.domain.exceptions.BusinessRuleTwoViolationExceptionDependantTasks;
import be.kuleuven.cs.mop.domain.exceptions.BusinessRuleTwoViolationExceptionTask;
import be.kuleuven.cs.mop.domain.exceptions.BusinessRuleViolationException;
import be.kuleuven.cs.mop.domain.exceptions.TaskManagerException;
import be.kuleuven.cs.mop.domain.model.InvitationConstraints;
import be.kuleuven.cs.mop.domain.model.Task;
import be.kuleuven.cs.mop.domain.model.TaskStatus;
import be.kuleuven.cs.mop.domain.model.TaskType;
import be.kuleuven.cs.mop.domain.model.User;
import be.kuleuven.cs.mop.domain.model.UserType;



/**
 * Internal implementation of the {@link Task} interface
 */
public class TaskImpl implements InvitationConstraints, Task, TimeListener {
	
	private final Set<TaskImpl>		dependants		= new HashSet<TaskImpl>();
	private final Set<TaskImpl>		dependencies	= new HashSet<TaskImpl>();
	private final Set<ReservationImpl>	reservations	= new HashSet<ReservationImpl>();
	

	private HashMap<String, Field> 	fields;
	private TaskTypeImpl 			type;
	private ProjectImpl				project;
	private Schedule				schedule;
	private TaskState				state;
	private UserImpl				user;
	

	
	/**
	 * Creates a task
	 * @param user - the {@link User} that wants to create this task
	 * @param schedule - a {@link Schedule} for this new task
	 * @param dependencies - a list of {@link Task}s where the new task depends on
	 * @param fields - a list of custom fields that are defined by the given {@link TaskType}
	 * @param type - a given {@link TaskType}
	 * 
	 * @throws TaskManagerException INVALID_USER - !{@link TaskImpl#isValidUser}
	 * @throws TaskManagerException TASKTYPE_DOES_NOT_ALLOW_OWNER_TYPE - !{@link TaskTypeImpl#isCreatableBy}
	 * @throws TaskManagerException INVALID_TASK_TYPE - !{@link #isValidTaskType}
	 * @throws TaskManagerException INVALID_TASK_SCHEDULE - !{@link #isValidSchedule}
	 * @throws TaskManagerException TASK_INVALID_DEPENDENCIES_SCHEDULE - !{@link #areValidDependencies}
	 * @throws TaskManagerException TASK_DEPENDENCY_LOOP - {@link #checkForDependenciesLoops}
	 * @throws TaskManagerException TASK_INVALID_RESOURCES - !{@link #areValidResources}
	 * @throws TaskManagerException FIELDS_DO_NOT_SATISFY_TASKTYPE_REQUIREMENTS - !{@link #areValidFields}
	 */
	public TaskImpl(final UserImpl user,
			final Schedule schedule,
			final Set<TaskImpl> dependencies,
			final HashMap<String,Field> fields, final TaskTypeImpl type) throws TaskManagerException {
		this(user, schedule, dependencies, fields, type, false);
	}
	/**
	 * 'Hidden' constructor used by the XML parser
	 * 
	 * @see {@link TaskImpl#TaskImpl(UserImpl, Schedule, Set, HashMap, TaskTypeImpl)}
	 */
	public TaskImpl(final UserImpl user,
			final Schedule schedule,
			final Set<TaskImpl> dependencies,
			final HashMap<String,Field> fields, final TaskTypeImpl type,
			final boolean initialState) throws TaskManagerException {
		setUser(user);
		setTaskType(type);
		setSchedule(schedule);
		setFields(fields);
		setDependencies(dependencies);
		
		if (initialState)
			setStatus(new TaskStateInitial(this));
		else
			setState();
		
		getWorld().getClock().addListener(this);
	}
	
	/**
	 * Sets the project this task belongs to
	 * @param projectImpl - A given project
	 * @throws TaskManagerException INVALID_PROJECT - Given project is null object
	 */
	public void setProject(ProjectImpl project) throws TaskManagerException {
		if(project == null)
			throw new TaskManagerException(TaskManagerException.INVALID_PROJECT);
		this.project = project;
	}

	/**
	 * Sets the schedule for this task
	 * @param startDate - a start date
	 * @param deadline - a deadline
	 * @param duration - a duration
	 * @throws TaskManagerException INVALID_TASK_SCHEDULE - {@link #isValidSchedule}
	 */
	private void setSchedule(final Schedule schedule) throws TaskManagerException {
		if(!isValidSchedule(schedule))
			throw new TaskManagerException(TaskManagerException.INVALID_TASK_SCHEDULE);
		
		this.schedule = schedule;
	}
	
	/**
	 * Sets the initial state of this task
	 * If one of the dependent tasks has state "failed", this task also gets the state  "failed"
	 * Else this task is Unfinished, meaning it is available of unavailable
	 * @see #isAvailable()
	 */
	private void setState() {
		if(containsFailedTask(getDependencies())) {
			this.state = new TaskStateFailed(this);
		}
		else {
			this.state = new TaskStateUnfinished(this);
		}
	}

	/**
	 * Checks whether the given set contains a task that is failed
	 * @param tasks - the set of tasks
	 * @return true if one of the tasks in the list is failed
	 */
	private static boolean containsFailedTask(Set<TaskImpl> tasks) {
		for(TaskImpl task : tasks) {
			if(task.getState().isFailed()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Sets the user this {@link Task} belongs to
	 * @param user - a {@link UserImpl}
	 * @throws TaskManagerException INVALID_USER - {@link TaskImpl#isValidUser}
	 */
	private void setUser(UserImpl user) throws TaskManagerException {
		if (!isValidUser(user))
			throw new TaskManagerException(TaskManagerException.INVALID_USER);
		this.user = user;
	}
	
	/**
	 * Sets the TaskType of this task
	 * @param type - A given TaskType
	 * @throws TaskManagerException TASKTYPE_DOES_NOT_ALLOW_OWNER_TYPE - {@link TaskTypeImpl#isCreatableBy()}
	 * @throws TaskManagerException INVALID_TASK_TYPE - {@link #isValidTaskType}
	 */
	private void setTaskType(TaskTypeImpl type) throws TaskManagerException {
		if(!isValidTaskType(type))
			throw new TaskManagerException(TaskManagerException.INVALID_TASK_TYPE);
			
		if(!type.isCreatableBy(getUser().getType()))
			throw new TaskManagerException(TaskManagerException.TASKTYPE_DOES_NOT_ALLOW_OWNER_TYPE);
		this.type = type;
	}

	/**
	 * Sets the given fields for this task
	 * @throws TaskManagerException FIELDS_DO_NOT_SATISFY_TASKTYPE_REQUIREMENTS - {@link #areValidFields}
	 */
	private void setFields(HashMap<String, Field> newFields) throws TaskManagerException {
		if(!areValidFields(newFields))
			throw new TaskManagerException(TaskManagerException.FIELDS_DO_NOT_SATISFY_TASKTYPE_REQUIREMENTS);
		this.fields = newFields;
	}
	
	/**
	 * Sets the dependencies for this {@link Task}
	 * @param dependencies - the list of {@link Task}s where this task depends on
	 * 
	 * @throws TaskManagerException TASK_INVALID_DEPENDENCIES_SCHEDULE - {@link #checkBusinessRuleOne(dependencies)}
	 * @throws TaskManagerException TASK_DEPENDENCY_LOOP - {@link #containsDependenciesLoops(dependencies)}
	 */
	private void setDependencies(final Collection<TaskImpl> dependencies) throws TaskManagerException{
		if(!checkBusinessRuleOne(dependencies)){
			throw new TaskManagerException(TaskManagerException.TASK_INVALID_DEPENDENCIES_SCHEDULE);
		}
		if(!containsDependenciesLoops(dependencies)){
			throw new TaskManagerException(TaskManagerException.TASK_DEPENDENCY_LOOP);
		}
		removeDependencyLinksToThisTask();
		clearDependencies();
		for (final TaskImpl dependency : dependencies)
			addDependency(dependency);
	}

	private void clearDependencies() {
		this.dependencies.clear();
	}
	
	/**
	 * Returns the state of this task
	 */
	protected TaskState getState() { return state; }
	
	@Override
	public TaskTypeImpl getType() {
		return type;
	}
	
	@Override
	public Set<TaskImpl> getDependants() {
		final Set<TaskImpl> dependants = new HashSet<TaskImpl>(this.dependants);
		final TaskImpl[] tasks = dependants.toArray(new TaskImpl[dependants.size()]);
		for (final TaskImpl task : tasks)
			dependants.addAll(task.getDependants());
		return Collections.unmodifiableSet(dependants);
	}
	
	@Override
	public Set<TaskImpl> getDependencies() {
		return Collections.unmodifiableSet(dependencies);
	}
	
	@Override
	public HashMap<String, Field> getFields() {
		return fields;
	}
	
	@Override
	public ProjectImpl getProject() throws TaskManagerException { 
		if(project == null)
			throw new TaskManagerException(TaskManagerException.TASK_DOES_NOT_BELONG_TO_PROJECT);
		return project; 
	}
	
	@Override
	public Set<ReservationImpl> getReservations() {
		return Collections.unmodifiableSet(reservations);
	}
	
	/**
	 * Returns a set of reservations of this task for the given resource type 
	 * @param type - The given resource type
	 */
	private Set<ReservationImpl> getReservationsOfResourceType(ResourceTypeImpl type) {
		HashSet<ReservationImpl> result = new HashSet<ReservationImpl>();
		for(ReservationImpl reserv : getReservations()){
			if(reserv.getResource().getResourceType().equals(type)){
				result.add(reserv);
			}
		}
		return result;
	}
	
	@Override
	public Schedule getSchedule() { return schedule; }
	
	
	@Override
	public TaskStatus getStatus() { return getState().toStatus(); }
	
	@Override
	public UserImpl getUser() { return user; }
	

	/**
	 * Adds a dependency for this {@link Task}
	 * This task is also added as a dependant task from the given Task (double link)
	 * If the dependency is already present, nothing happens
	 * @param task - a {@link Task}
	 */
	private void addDependency(final TaskImpl task) {
		if (dependencies.add(task))
			task.dependants.add(this);
	}
	
	/**
	 * Determines whether or not this <code>Task</code>
	 * depends on the specified <code>Task</code> (recursively)
	 * @param task - a <code>Task</code> which is possibly a dependency
	 */
	private boolean dependsOn(final TaskImpl task) {
		for (final TaskImpl dependency : dependencies)
			if (dependency.equals(task) || dependency.dependsOn(task))
				return true;
		return false;
	}
	
	/**
	 * Returns the earliest end-time for the specified <code>Task</code>
	 * @return a <code>Calendar</code> representing the earliest end-time
	 */
	private Calendar getEarliestEndTime() {
		final Calendar eet = new GregorianCalendar();
		eet.setTimeInMillis(
				getEarliestEndTime(getDependencies()).getTimeInMillis()
				+ getSchedule().getDuration());
		return eet;
	}
	
	/**
	 * Returns the earliest end-time for all <code>Task</code>s
	 * @param tasks - a list of <code>Task</code>s
	 */
	private static Calendar getEarliestEndTime(final Iterable<TaskImpl> tasks) {
		Calendar test, latest = new GregorianCalendar();
		latest.setTimeInMillis(0);
		for (final TaskImpl currentTask : tasks)
			if ((test = currentTask.getEarliestEndTime()).after(latest))
				latest = test;
		return latest;
	}
	
	private InvitationManagerImpl getInvitationManager() {
		return getWorld().getInvitationManager();
	}
	
	public final World getWorld() {
		return getUser().getWorld();
	}
	
	/**
	 * Checks whether this task is available
	 * 
	 * @return true if the current time is covered by this task's schedule, all dependency tasks are finished succesfully, 
	 * the minimum of helper users is met, and all necessary resources are available
	 */
	@Override
	public boolean isAvailable() {
		return getState().isAvailable();
	}

	/**
	 * Checks whether the current time is covered by the schedule of this task
	 * @return true if the current time is covered by the schedule of this task
	 */
	public boolean scheduleCoversCurrentTime() {
		final Calendar	now		= getCurrentTime();
		return getSchedule().covers(now);
	}
	
	/**
	 * Modifies the details of this {@link Task} with the specified details
	 * @param fields - The new fields for this task
	 * @param schedule - The new schedule for this task
	 * @param dependencies - The new list of dependencies for this task
	 * 
	 * @throws TaskManagerException INVALID_TASK_SCHEDULE - !{@link #isValidSchedule}
	 * @throws TaskManagerException TASK_CAN_NOT_BE_MODIFIED - !{@link TaskState#isModifiable}
	 * @throws TaskManagerException FIELDS_DO_NOT_SATISFY_TASKTYPE_REQUIREMENTS - !{@link #areValidFields}
	 * @throws TaskManagerException TASK_INVALID_DEPENDENCIES_SCHEDULE - !{@link #areValidDependencies}
	 * @throws TaskManagerException TASK_DEPENDENCY_LOOP - !{@link #checkForDependenciesLoops}
	 * @throws BusinessRuleTwoViolationExceptionTask - {@link #checkContainsFailedTask(dependencies)}
	 * @note Memento pattern
	 */
	public void modify(final HashMap<String,Field> fields, final Schedule schedule,
			final Set<TaskImpl> dependencies) throws TaskManagerException, BusinessRuleTwoViolationExceptionTask {
		if (!getState().isModifiable())
			throw new TaskManagerException(TaskManagerException.TASK_CAN_NOT_BE_MODIFIED);
		
		final MementoTaskImpl memento =
			new MementoTaskImpl(getDependencies(), getFields(), getSchedule());
		
		try {
			checkContainsFailedTask(dependencies);
			
			setSchedule(schedule);
			setFields(fields);
			setDependencies(dependencies);
			
			getInvitationManager().resetAcceptedInvitations(this);
		} catch(final TaskManagerException e) {
			restoreMementoTaskImpl(memento);
			throw e;
		} catch(final BusinessRuleTwoViolationExceptionTask e) {
			restoreMementoTaskImpl(memento);
			throw e;
		}
	}

	/**
	 * Checks whether the given list of tasks contains a task who's state is failed
	 * @param tasks - the given list of tasks
	 * @throws BusinessRuleTwoViolationExceptionTask - one of the tasks in the given list of tasks has status failed
	 */
	private static void checkContainsFailedTask(Set<TaskImpl> tasks) throws BusinessRuleTwoViolationExceptionTask {
		for(TaskImpl task : tasks) {
			if(task.getState().isFailed()) {
				throw new BusinessRuleTwoViolationExceptionTask(task);
			}
		}
	}

	/**
	 * Restores this task to a previous state
	 * @param memento - The MementoTaskImpl object describing a previous task state
	 */
	private void restoreMementoTaskImpl(MementoTaskImpl memento) {
		try {
			setSchedule(memento.getSchedule());
			setFields(memento.getFields());
			setDependencies(memento.getDependencies());		
		} catch (TaskManagerException e) {
			// Should not happen!
			e.printStackTrace();
			throw new InternalError();
		}
	}

	/**
	 * Makes the resource reservation and checks the task constraints
	 * 
	 * @param resource - The resource to be reserved
	 * @param date - The date info for the reservation
	 * @param duration - The duration of the reservation (in milliseconds)
	 * 
	 * @throws TaskManagerException RESERVATION_CONFLICT - {@link ResourceImpl#addReservation(TaskImpl, Calendar, long)}
	 * @throws TaskManagerException RESOURCE_NOT_REQUIRED_BY_TASK - The resource is not required by a task of this task type.
	 * @throws TaskManagerException UPPER_LIMIT_OF_RESERVED_RESOURCES_REACHED_FOR_RESOURCETYPE - The upper limit of reserved resources of the type of this resource has already been reached
	 * @throws TaskManagerException RESOURCE_RESERVATIONS_OVERLAP_NOT_LONG_ENOUGH - The overlap with the dates of the other resource reservations is not long enough to finish the task in time (by using all these resources)
	 * @Note: Creator pattern
	 */
	public void makeResourceReservation(ResourceImpl resource, Calendar date, long duration) throws TaskManagerException{
		if(!getType().getRequiredResourceTypes().keySet().contains(resource.getResourceType())){
			throw new TaskManagerException(TaskManagerException.RESOURCE_NOT_REQUIRED_BY_TASK);
		}
		if(reservationsUpperLimitReachedForType(resource.getResourceType())){
			throw new TaskManagerException(TaskManagerException.UPPER_LIMIT_OF_RESERVED_RESOURCES_REACHED_FOR_RESOURCETYPE);
		}
		if(!overlapStillLongEnoughWithNewReservation(date, duration)){
			throw new TaskManagerException(TaskManagerException.RESOURCE_RESERVATIONS_OVERLAP_NOT_LONG_ENOUGH);
		}
			
		addReservation(resource.addReservation(this, date, duration));
	}
	
	
	/**
	 * Checks whether a new reservation would exclude the possibility to execute the task.
	 * @param date - The date of the new reservation
	 * @param duration - The duration of the new reservation
	 * @return false if the overlap of this new reservation with older reservations would exclude the possibility to execute this task (by using all resources at the same time)
	 */
	private boolean overlapStillLongEnoughWithNewReservation(Calendar date,
			long duration) {
		Calendar latestStartDate = getReservationsLatestStartDate();
		Calendar earliestEndDate = getReservationsEarliestEndDate();
		Calendar newReservationEndDate = ((Calendar)(date.clone()));
		newReservationEndDate.setTimeInMillis(date.getTimeInMillis()+duration);
		if(date.after(latestStartDate)){
			latestStartDate = date;
		}
		if(newReservationEndDate.before(earliestEndDate)){
			earliestEndDate = newReservationEndDate;
		}
		return (getSchedule().getDuration() <= (earliestEndDate.getTimeInMillis() - latestStartDate.getTimeInMillis()));
		
	}

	/**
	 * Getter for the date of the reservation that is scheduled last 
	 */
	private Calendar getReservationsLatestStartDate(){
		//initialized on earliest possible date (which is the Task startdate)
		Calendar latestStartDate = (Calendar)getSchedule().getStartDate().clone();
		for(ReservationImpl reserv : getReservations()){
			if(reserv.getDate().after(latestStartDate)){
				latestStartDate = (Calendar)reserv.getDate().clone();
			}
		}
		return latestStartDate;
	}
	
	/**
	 * Getter for the date of the reservation that ends first 
	 */
	private Calendar getReservationsEarliestEndDate(){
		//initialized on deadline of Task, as that is the earliest possible end date in regards to overlap 
		Calendar earliestEndDate = (Calendar)getSchedule().getDeadline().clone();
		earliestEndDate.setTimeInMillis(0x0FFFFFFFFFFFFFFFL);
		for(ReservationImpl reserv : getReservations()){
			if(reserv.getEnd().before(earliestEndDate)){
				earliestEndDate = (Calendar)reserv.getEnd().clone();
			}
		}
		return earliestEndDate;
	}
	
	
	
	/**
	 * Executed when the time of the system is changed
	 * Notifies state
	 */
	@Override
	public void onTimeChanged(final Clock time) {
		getState().onTimeChanged(time);
	}
	
	/**
	 * Removes this task
	 * Breaks all links to this task and removes this task from the owner user task list
	 */
	public void remove() {
		removeDependencyLinksToThisTask();
		removeDependantLinksToThisTask();
		getInvitationManager().removeAllInvitationsOfTask(this);
		removeAllReservationsLinksToThisTask();
		getUser().remove(this);
		getWorld().getClock().removeListener(this);
	}
	
	/**
	 * Removes this task recursively
	 * All tasks that depend on this task are removed recursively
	 */
	public void removeRecursive() {
		for (final TaskImpl dependant : getDependants()){
			dependant.removeRecursive();
		}
		remove();
	}
	
	/**
	 * Removes all links to this task from the dependency tasks of this task
	 */
	private void removeDependencyLinksToThisTask() {
		for(TaskImpl dependency : getDependencies().toArray(new TaskImpl[0])) {
			dependency.removeDependantLinks(this);
		}
	}
	
	/**
	 * Removes all links to this task from the tasks that depend on this task
	 */
	private void removeDependantLinksToThisTask() {
		for(TaskImpl dependant : getDependants()) {
			dependant.removeDependencyLinks(this);
		}
	}
	
	/**
	 * Removes a dependency from this task
	 * Breaks down the double link
	 * @param task - The dependency task to be removed
	 */
	public void removeDependencyLinks(TaskImpl task) {
		removeDependency(task);
		task.removeDependant(this);
	}

	/**
	 * Removes a dependant from this task
	 * Breaks down the double link
	 * @param task - The dependant task to be removed
	 */
	public void removeDependantLinks(TaskImpl task){
		removeDependant(task);
		task.removeDependency(this);
	}

	/**
	 * Removes a dependant task from this task
	 * @param task - The dependant task to be removed
	 */
	private void removeDependant(TaskImpl task) {
		dependants.remove(task);
	}
	
	/**
	 * Removes a dependent task from this task
	 * @param task - The dependent task to be removed
	 */
	private void removeDependency(TaskImpl task) {
		dependencies.remove(task);
	}
	
	/**
	 * Updates the state of this task
	 * @param status - The new status
	 * 
	 * @throws TaskManagerException INVALID_TASK_STATUS - If the TaskStatus supplied does not match a valid TaskState
	 * @throws BusinessRuleThreeViolationExceptionTaskScheduleDoesNotCoverCurrentTime 
	 * @throws BusinessRuleTwoViolationExceptionTask 
	 * @throws BusinessRuleThreeViolationExceptionDeadlineExpired 
	 * @throws BusinesRuleViolationException - if a BusinessRuleViolation is caused by this Task State Update.
	 * @link {@link TaskState#setFailedSafely()}
	 * @link {@link TaskState#setUnfinishedSafely()}
	 * @link {@link TaskState#setSuccesfulSafely()}
	 * @note : State pattern
	 */
	public void updateTaskStatus(final TaskStatus status)
			throws TaskManagerException, BusinessRuleTwoViolationExceptionDependantTasks, BusinessRuleTwoViolationExceptionTask, BusinessRuleThreeViolationExceptionTaskScheduleDoesNotCoverCurrentTime, BusinessRuleThreeViolationExceptionDeadlineExpired {
		switch (status) {
			case FAILED:
				getState().setFailedSafely();
				break;
			case SUCCESSFUL:
				getState().setSuccessfulSafely();
				break;
			case UNFINISHED:
				getState().setUnfinishedSafely();
				break;
			default:
				throw new TaskManagerException(TaskManagerException.INVALID_TASK_STATUS);
		}
	}
	
	/**
	 * Updates the state of this task recursively.
	 * @param status
	 * @throws TaskManagerException	BUSINESS_RULE_VIOLATION_CAN_NOT_BE_HANDLED_RECURSIVELY - The violation caused be the task state update isn't one that can be handled recursively.
	 */
	public void updateTaskStatusRecursively(final TaskStatus status) throws TaskManagerException {
		try{
			updateTaskStatus(status);
		}
		// The only business rule exception 
		catch(BusinessRuleTwoViolationExceptionDependantTasks exc){
			executeRecursiveTaskStatusUpdate(exc, status);
			try{
				updateTaskStatus(status);
			}
			catch(TaskManagerException e) {
				throw new InternalError();
				//shouldn't happen
			}
			catch(BusinessRuleViolationException a){
				throw new InternalError();
				//shouldn't happen
			}
		}
		catch(TaskManagerException e) {
			throw new TaskManagerException(TaskManagerException.BUSINESS_RULE_VIOLATION_CAN_NOT_BE_HANDLED_RECURSIVELY);
		}
		catch(BusinessRuleViolationException a){
			throw new TaskManagerException(TaskManagerException.BUSINESS_RULE_VIOLATION_CAN_NOT_BE_HANDLED_RECURSIVELY);
		}
	}
	
	/**
	 * 	Resolves the business rule violation by recursively updating the tasks.
	 * 
	 * @param excep The exception thrown by the error we are trying to resolve.
	 * @param status
	 * @throws TaskManagerException
	 */
	private void executeRecursiveTaskStatusUpdate(BusinessRuleTwoViolationExceptionDependantTasks excep, TaskStatus status) throws TaskManagerException{
		for(Task dependant : excep.getSuggestions().keySet()){
			if(getDependants().contains(dependant)){
				// Check if dependant is direct child
				((TaskImpl)dependant).updateTaskStatusRecursively(excep.getSuggestions().get(dependant));
			}
		}
		
	}
	
	/**
	 * Changes the state of this task
	 * @param status - The new state of this task
	 * @Note: declared protected to make sure a State object can call this method when changing state
	 */
	protected void setStatus(final TaskState status) {
		if (status == null)
			throw new NullPointerException("TaskState == NULL");
		
		this.state = status;
	}
	
	@Override
	public String toString() {
		return String.format("Task[type='%s', id=%d]",
				getType().getName(),
				hashCode());
	}
	
	/**
	 * Validates the given list of dependencies
	 * @param dependencies - A list of dependencies
	 * @return == (dependencies != null && for every dependency in dependencies: thisTaskEndsAfterGivenTask(dependency))
	 */
	private boolean checkBusinessRuleOne(final Collection<TaskImpl> dependencies){
		if(dependencies == null)
			return false;
		for (final TaskImpl dependency : dependencies)
			if(!thisTaskEndsAfterGivenTask(dependency)){
				return false;
			}
		return true;
	}

	/**
	 * Validates a given {@link User}
	 * @param user - a {@link User}
	 * @return false if user object is null or not present in the system
	 */
	private static boolean isValidUser(final UserImpl user) {
		return (user != null && user.getWorld().isPresent((user)));
	}
	
	/**
	 * Checks for dependency loops on the given dependency list
	 * @param dependencies - A list of dependency tasks
	 * @return true when dependencies != null && for every dependency in dependencies: checkForDependencyLoop(dependency)
	 */
	private boolean containsDependenciesLoops(final Collection<TaskImpl> dependencies) {
		if(dependencies == null)
			return false;
		for (final TaskImpl dependency : dependencies){
			if(!containsDependencyLoop(dependency)){
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Checks whether a given dependency task is introducing a loop
	 * Returns true if no dependency loop is introduced by the given dependency
	 * 
	 * @param dependency - A given dependency
	 * @return == (!dependency.equals(this) && !dependency.dependsOn(this)
	 */
	private boolean containsDependencyLoop(final TaskImpl dependency) {
		return (!dependency.equals(this) && !dependency.dependsOn(this));
	}
	
	/**
	 * Checks whether this Task ends after the given task.
	 * @param dependency - A given dependency
	 * @return true when the given task has to finish before this task
	 * @Note : Enforces businessrule one
	 */
	private boolean thisTaskEndsAfterGivenTask(final TaskImpl dependency) {
		final Calendar eet = new GregorianCalendar();
		eet.setTimeInMillis(dependency.getEarliestEndTime().getTimeInMillis()
				+ getSchedule().getDuration());
		return !eet.after(getSchedule().getDeadline());
	}

	/**
	 * Validates custom fields
	 * @param newFields - A hashmap of new custom fields
	 * @return false if fields does not match the predefined fields of the tasktype of this task
	 */
	private boolean areValidFields(HashMap<String, Field> newFields) {
		return getType().matchesFields(newFields);
	}
	
	/**
	 * Checks whether the given TaskType is valid and present in the system
	 * @param type - A {@link TaskTypeImpl}
	 * @return false if type is null or not present in the system
	 */
	private boolean isValidTaskType(TaskTypeImpl type){
		return (type != null && getUser().getWorld().isPresent((type)));
	}
	
	/**
	 * Checks whether the given schedule is valid
	 * @param schedule - A schedule
	 * @return false if schedule is null or deadline of this task is already expired
	 */
	private boolean isValidSchedule(Schedule schedule) {
		return (schedule != null && !deadlineExpired(schedule));
	}
	
	/**
	 * Check whether deadline of the given schedule is already expired
	 * @param schedule - The given schedule
	 * @return true if deadline is already expired
	 */
	private boolean deadlineExpired(final Schedule schedule) {
		return schedule.getDeadline().before(getCurrentTime());
	}

	/**
	 * Returns the current time of the system
	 * 
	 * @Note: protected so state can access this method
	 */
	protected Calendar getCurrentTime() {
		return getWorld().getClock().getTime();
	}
	
	/**
	 * 	Returns true if the deadline of this Task has already been expired.
	 */
	public boolean deadlineExpired() {
		return deadlineExpired(getSchedule());
	}

	/**
	 * 	Returns the constraints on helper users as specified by the <code>TaskTypeImpl</code> of this task
	 */
	@Override
	public Map<UserTypeImpl, Interval> getHelperConstraints() {
		return getType().getHelperUserTypesConstraints();
	}

	/**
	 * 	Returns the invitation constraints on a specific <code>UserType</code> as specified by the <code>TaskTypeImpl</code> of this task
	 */
	@Override
	public Interval getInvitationConstraints(UserType type) {
		return getType().getHelperUserTypesConstraints().get(type);
	}
	
	/**
	 * Removes all reservation links to this task
	 */
	private void removeAllReservationsLinksToThisTask(){
		for(ReservationImpl reserv : getReservations()){
			try{
				reserv.getResource().removeReservation(reserv);
			}
			catch(TaskManagerException e){
				assert(false);
				//Impossible
			}
		}
	}
	
	/**
	 * Simply adds the reservation to the list of reservations of this task
	 * @param reservation - The new reservation to be added
	 */
	private void addReservation(ReservationImpl reservation){
		this.reservations.add(reservation);
	}
	
	/**
	 * Checks whether the upper limit of reservations, defined by the task type of this task, is reached 
	 * @param type - The resourcetype of the resource that one wants to reserve
	 * @return true if the upper limit for this kind of resource type is already reached
	 */
	private boolean reservationsUpperLimitReachedForType(ResourceTypeImpl type){
		return (getType().getRequiredResourceTypes().get(type).getUpperLimit() <= getReservationsOfResourceType(type).size());
	}
	
	/**
	 * Returns true if this <code>TaskImpl</code> satisfies it's <code>TaskTypeImpl</code>'s requirements on Resource Reservations.
	 */
	public boolean satisfiesReservationRequirements() {
		return getType().matchesReservations(getReservations());
	}
	
	/**
	 * Returns true if this <code>TaskImpl</code> satisfies it's <code>TaskTypeImpl</code>'s requirements on Helper User Invitations.
	 */
	public boolean satisfiesAcceptedHelperUserRequirements(){
		HashSet<UserImpl> acceptedUsers = new HashSet<UserImpl>();
		for(InvitationImpl invit : getInvitationManager().getInvitationsAccepted(this)){
			acceptedUsers.add(invit.getUser());
		}
		
		return getType().matchesHelperUsers(acceptedUsers);
	}
}